package org.xteam.sled.model;

import java.util.ArrayList;
import java.util.List;

import org.xteam.sled.semantic.exp.IExpRewriter;

public abstract class BaseSequence<T extends ISequent> implements ISequence {
	
	protected boolean hasPrefix;
	protected boolean hasSuffix;
	protected List<T> elements;
	
	public BaseSequence(boolean hasPrefix, List<T> elements, boolean hasSuffix) {
		this.hasPrefix = hasPrefix;
		this.elements = elements;
		this.hasSuffix = hasSuffix;
	}
	
	public BaseSequence(T element) {
		this.hasPrefix = false;
		this.elements = new ArrayList<T>();
		this.hasSuffix = false;
		this.elements.add(element);
	}
	
	public List<ISequent> getElements() {
		return (List<ISequent>) elements;
	}
	
	@Override
	public boolean hasPrefix() {
		return hasPrefix;
	}

	@Override
	public boolean hasSuffix() {
		return hasSuffix;
	}
	
	@Override
	public boolean isFieldBinding() {
		if (! hasPrefix() && ! hasSuffix() && getElements().size() == 1) {
			ISequent sequent = getElements().get(0);
			if (sequent.isLabel())
				return false;
			return ((Constraints)sequent).getConstraints().isEmpty() && ((Constraints)sequent).fieldBindings.size() == 1;
		}
		return false;
	}
	
	@Override
	public ISequence postDots() {
		return createSequence(hasPrefix, true, (List<ISequent>)elements);
	}

	@Override
	public ISequence preDots() {
		return createSequence(true, hasSuffix, (List<ISequent>)elements);
	}
	
	public int bitLength() {
		int l = 0;
		for (ISequent cnsts : elements) {
			l += cnsts.width();
		}
		return l;
	}
	
	public void add(T element) {
		this.elements.add(element);
	}
	
	private void prepend(T element) {
		this.elements.add(0, element);
	}
	
	protected abstract BaseSequence<T> createSequence();
	
	@Override
	public ISequence mergeLeft(ISequence o) {
		BaseSequence<T> other = (BaseSequence<T>) o;
		BaseSequence<T> result = createSequence();
		int i = 0;
		int j = 0;
		while (i < elements.size() || j < other.elements.size()) {
			if (i >= elements.size()) {
				if (! hasSuffix)
					throw new RuntimeException("Shapes differ for &; left-hand sequence too short");
				result.add(other.elements.get(j++));
			} else if (j >= other.elements.size()) {
				if (! other.hasSuffix)
					throw new RuntimeException("Shapes differ for &; right-hand sequence too short");
				result.add(elements.get(i++));
			} else if (! (elements.get(i).isLabel() || other.elements.get(j).isLabel())) {
				result.add((T)elements.get(i++).merge(other.elements.get(j++)));
			} else if (elements.get(i).isLabel()) {
				result.add(elements.get(i++));
			} else {
				result.add(other.elements.get(j++));
			}
		}
		return result;
	}

	@Override
	public ISequence mergeRight(ISequence o) {
		BaseSequence<T> other = (BaseSequence<T>) o;
		BaseSequence<T> result = createSequence();
		int i = elements.size()-1;
		int j = other.elements.size()-1;
		while (i >= 0 || j >= 0) {
			if (i < 0) {
				if (! hasPrefix)
					throw new RuntimeException("Shapes differ for &; left-hand sequence too short");
				result.prepend(other.elements.get(j--));
			} else if (j < 0) {
				if (! other.hasPrefix)
					throw new RuntimeException("Shapes differ for &; right-hand sequence too short");
				result.prepend(elements.get(i--));
			} else if (! (elements.get(i).isLabel() || other.elements.get(j).isLabel())) {
				result.prepend((T)elements.get(i--).merge(other.elements.get(j--)));
			} else if (elements.get(i).isLabel()) {
				result.prepend(elements.get(i--));
			} else {
				result.prepend(other.elements.get(j--));
			}
		}
		return result;
	}

	@Override
	public boolean isContradictory() {
		for (T cnts : elements) {
			if (cnts.isContradictory())
				return true;
		}
		return false;
	}
	
	@Override
	public ISequence subst(IExpRewriter subs) {
		List<ISequent> n = new ArrayList<ISequent>();
		for (ISequent e : elements) {
			n.add(e.subs(subs));
		}
		return createSequence(hasPrefix, hasSuffix, n);
	}
	
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		if (hasPrefix)
			buffer.append("...");
		boolean isFirst = true;
		for (T element : elements) {
			if (! isFirst)
				buffer.append("; ");
			buffer.append(element.toString());
			isFirst = false;
		}
		if (hasSuffix)
			buffer.append("...");
		return buffer.toString();
	}
	
}
